/*
 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ohos_video_data.h"
#include <thread>

#include "hilog/log.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏，标识业务领域
#define LOG_TAG "MY_TAG"  // 全局tag宏，标识模块日志tag

namespace ohos {

CodecData::CodecData() {
}

CodecData::~CodecData() {
	Release();
}

bool CodecData::InputReady() const {
	bool result = false;
	std::unique_lock<std::mutex> lock(inputMutex_);
	result = !inputBufferQueue_.empty();

	return result;
}

int32_t CodecData::InputData(
    const uint8_t *data, size_t size, OH_AVCodecBufferAttr *attr, int *indx, std::chrono::milliseconds waitTime) {
	if (attr == nullptr) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "OH_AVCodecBufferAttr can't be NULL!");
		return ERRCODE_PARAM_INVALID;
	}

	std::unique_lock<std::mutex> lock(inputMutex_);
	bool waitForTimeout = !inputCond_.wait_for(lock, waitTime, [this]() { return !this->inputBufferQueue_.empty(); });

	if (waitForTimeout) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue wait %{public}lld ms timeout!",
		             waitTime.count());
		return ERRCODE_TIMEOUT;
	}
	if (inputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, "DecoderData", "inputBufferInfoQueue is empty");
		return ERRCODE_QUEUE_EMPTY;
	}

	AvBufferInfo bufferInfo = inputBufferQueue_.front();
	inputBufferQueue_.pop();
	inputFrameCount_++;
	lock.unlock();

	bufferInfo.Attr(attr);
	OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, "DecoderData", "AvBufferSize: %{public}d", bufferInfo.AvBufferSize());

	if (data != nullptr && size > 0) {
		uint8_t *avBufferAddr = bufferInfo.AvBufferAddr();
		// 判断avbuffer大小，循环写入
		if (avBufferAddr == nullptr || size > bufferInfo.AvBufferSize()) {
			OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "AV buffAddr is nullptr");
			return ERRCODE_UNKNOWN;
		}
		memcpy(avBufferAddr, data, size);
	}

	if (indx != nullptr) {
		*indx = bufferInfo.Index();
	}

	if (attr->flags & AVCODEC_BUFFER_FLAGS_EOS) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "Catch EOS");
	}

	return ERRCODE_OK;
}

bool CodecData::OutputReady() const {
	std::unique_lock<std::mutex> lock(outputMutex_);
	return !outputBufferQueue_.empty();
}

int32_t CodecData::OutputData(AvBufferInfo &buff, std::chrono::milliseconds waitTime) {
	std::unique_lock<std::mutex> lock(this->outputMutex_);
	bool waitForTimeout = !outputCond_.wait_for(lock, waitTime, [this]() { return !this->outputBufferQueue_.empty(); });

	if (waitForTimeout && outputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue wait %{public}lld ms timeout!",
		             waitTime.count());
		return ERRCODE_TIMEOUT;
	}

	if (outputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue_ is empty");
		return ERRCODE_QUEUE_EMPTY;
	}

	buff = outputBufferQueue_.front();
	outputBufferQueue_.pop();
	outputFrameCount_++;
	return ERRCODE_OK;
}

int32_t CodecData::GetOutputBuffer(AvBufferInfo &buff, std::chrono::milliseconds waitTime) {
	std::unique_lock<std::mutex> lock(this->outputMutex_);
	bool waitForTimeout = !outputCond_.wait_for(lock, waitTime, [this]() { return !this->outputBufferQueue_.empty(); });

	if (waitForTimeout && outputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue wait timeout!");
		return ERRCODE_TIMEOUT;
	}

	if (outputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue_ is empty");
		return ERRCODE_QUEUE_EMPTY;
	}
	buff = outputBufferQueue_.front();
	outputBufferQueue_.pop();
	outputFrameCount_++;
	return ERRCODE_OK;
}

int32_t CodecData::GetInputBuffer(AvBufferInfo &buff, std::chrono::milliseconds waitTime) {
	std::unique_lock<std::mutex> lock(this->inputMutex_);
	bool waitForTimeout = !inputCond_.wait_for(lock, waitTime, [this]() { return !this->inputBufferQueue_.empty(); });

	if (waitForTimeout && inputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue wait timeout!");
		return ERRCODE_TIMEOUT;
	}

	if (inputBufferQueue_.empty()) {
		OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue is empty");
		return ERRCODE_QUEUE_EMPTY;
	}

	buff = inputBufferQueue_.front();
	inputBufferQueue_.pop();
	inputFrameCount_++;
	return ERRCODE_OK;
}

int32_t CodecData::NeedInputBuffer(uint32_t index, OH_AVBuffer *buffer) {
	std::unique_lock<std::mutex> lock(inputMutex_);
	inputBufferQueue_.emplace(index, buffer);
	inputCond_.notify_all();

	return 0;
}

int32_t CodecData::NewOutputBuffer(uint32_t index, OH_AVBuffer *buffer) {
	std::unique_lock<std::mutex> lock(outputMutex_);
	outputBufferQueue_.emplace(index, buffer);
	outputCond_.notify_all();

	return 0;
}

uint32_t CodecData::InputFrame() const {
	return inputFrameCount_;
}

uint32_t CodecData::OutputFrame() const {
	return outputFrameCount_;
}

void CodecData::Release() {
	inputFrameCount_ = 0;
	outputFrameCount_ = 0;

	while (!inputBufferQueue_.empty()) {
		inputBufferQueue_.pop();
	}
	while (!outputBufferQueue_.empty()) {
		outputBufferQueue_.pop();
	}
}
} // namespace ohos
